import { Types } from "vm";

type Thenable<T> = T & { then(f: (p: T) => void): Promise<void> };

type VM_addValueChangeListener = (
  name: string,
  callback: Types.Vcl.cbFunction,
) => string;

type VM_removeValueChangeListener = (
  id: ReturnType<VM_addValueChangeListener>,
) => void;

type VM_getResourceURL = (
  name: string,
  isBlobUrl?: boolean,
) => `${"blob" | "data"}:${string}` | undefined;
type VM_getResourceText = (name: string) => string | undefined;

type VM_openInTab_O = (
  url: string,
  options?: Types.OpenTab.Option,
) => Types.OpenTab.Result;

type VM_openInTab_L = (
  url: string,
  loadInBackground?: boolean,
) => Types.OpenTab.Result;

type VM_registerMenuCommand = (
  caption: string,
  onClick: (e?: MouseEvent | KeyboardEvent) => void | Promise<void>,
  options?: Types.Menu.Options,
) => Required<Types.Menu.Options>["id"];
type VM_unregisterMenuCommand = (
  id: ReturnType<VM_registerMenuCommand>,
) => void;

type VM_xmlhttpRequest = (details: Types.Xhr.Details) => CM_Types.Xhr.Return;
type VM_download_D = (details: Types.Download.Details) => CM_Types.Xhr.Return;
type VM_download_U = (
  url: Types.Download.Details["url"],
  name?: Types.Download.Details["name"],
) => CM_Types.Xhr.Return;

type VM_addStyle = (css: string) => Thenable<HTMLStyleElement>;
type VM_addElement_T = <K extends keyof HTMLElementTagNameMap>(
  tagName: K,
  attributes?: Record<
    Parameters<typeof Element.prototype.setAttribute>[0],
    string | number | boolean
  >,
) => Thenable<HTMLElementTagNameMap[K]>;
type VM_addElement_P = <K extends keyof HTMLElementTagNameMap>(
  parentNode: Node | Element | ShadowRoot,
  tagName: K,
  attributes?: Record<
    Parameters<typeof Element.prototype.setAttribute>[0],
    string | number | boolean
  >,
) => Thenable<HTMLElementTagNameMap[K]>;

type VM_Notification_O = (
  options: Types.Notification.Option,
) => Types.Notification.Return;

type VM_Notification_T = (
  text: Types.Notification.Option["text"],
  title?: Types.Notification.Option["title"],
  image?: Types.Notification.Option["image"],
  onclick?: (this: Types.Notification.Option) => void,
) => Types.Notification.Return;

type VM_setClipboard = (data: string, type: string) => void;

interface VM_Platform {
  readonly arch: "arm" | "mips" | "mips64" | "x86-32" | "x86-64";
  readonly brand: string;
  readonly browserBrand: string;
  readonly browserName: string;
  readonly browserVersion: string;
  readonly name: string;
  readonly os: "android" | "cros" | "linux" | "mac" | "openbsd" | "win";
  readonly version: string;
}

interface VM_ScriptInfo {
  readonly description: string;
  readonly excludes: string[];
  readonly excludeMatches: string[];
  readonly grant: string[];
  readonly includes: string[];
  readonly matches: string[];
  readonly name: string;
  readonly namespace: string;
  readonly noframes?: boolean;
  readonly require: string[];
  readonly resources: Types.Resource[];
  readonly runAt: `document-${"start" | "end" | "idle"}` | "";
  readonly unwrap?: boolean;
  readonly version: string;
}

type VM_ScriptInfoCustom = VM_ScriptInfo &
  Partial<Readonly<Record<string, string | string[]>>>;

declare interface VM_Info {
  readonly injectInto: "page" | "content" | "auto";
  readonly isIncognito: boolean;
  readonly platform: VM_Platform;
  readonly script: VM_ScriptInfoCustom;
  readonly scriptHandler: "Violentmonkey";
  readonly scriptMetaStr: string;
  readonly scriptWillUpdate: boolean;
  readonly uuid: string;
  readonly version: string;
}

interface VM_Object extends CM_Object {
  getResourceURL: VM_getResourceURL;
  getResourceUrl: Promisize<VM_getResourceURL>;
  openInTab: VM_openInTab_O | VM_openInTab_L;
  registerMenuCommand(name: string, onClick: () => void): string;
  xmlHttpRequest: VM_xmlhttpRequest;
  addStyle: VM_addStyle;
  GM_addElement: VM_addElement_T & VM_addElement_P;
  notification: VM_Notification_O & VM_Notification_T;
  setClipboard: VM_setClipboard;
  readonly info: VM_Info;
}

interface VM_SafeWindow extends Window {
  close(): void;
  focus(): void;
  external: External & {
    Violentmonkey?: {
      readonly version: VM_Info["version"];
      isInstalled: (
        name: VM_ScriptInfo["name"],
        namespace: VM_ScriptInfo["namespace"],
      ) => Promise<VM_ScriptInfo["version"] | null>;
    };
  };
}

declare module "vm" {
  export const GM: VM_Object;
  export const GM_getValue: CM_getValue;
  export const GM_setValue: CM_setValue;
  export const GM_listValues: CM_listValues;
  export const GM_deleteValue: CM_deleteValue;
  export const GM_addValueChangeListener: VM_addValueChangeListener;
  export const GM_removeValueChangeListener: VM_removeValueChangeListener;
  export const GM_getResourceURL: VM_getResourceURL;
  export const GM_getResourceText: VM_getResourceText;
  export const GM_openInTab: VM_openInTab_O & VM_openInTab_L;
  export const GM_registerMenuCommand: VM_registerMenuCommand;
  export const GM_unregisterMenuCommand: VM_unregisterMenuCommand;
  export const GM_xmlhttpRequest: VM_xmlhttpRequest;
  export const GM_download: VM_download_D & VM_download_U;
  export const GM_addStyle: VM_addStyle;
  export const GM_addElement: VM_addElement_T & VM_addElement_P;
  export const GM_notification: VM_Notification_O & VM_Notification_T;
  export const GM_setClipboard: VM_setClipboard;
  export const GM_log: typeof console.log;
  export const GM_info: VM_Info;
  export let unsafeWindow: unsafeWindow;
}
